热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

主存|局部性_交易系统开发技能及面试之低延迟编程技术

篇首语:本文由编程笔记#小编为大家整理,主要介绍了交易系统开发技能及面试之低延迟编程技术相关的知识,希望对你有一定的参考价值。文章目录

篇首语:本文由编程笔记#小编为大家整理,主要介绍了交易系统开发技能及面试之低延迟编程技术相关的知识,希望对你有一定的参考价值。



文章目录


  • 概要
  • CPU Caches
    • Q1 分析代码段性能
    • Q2 采用alignas进行性能优化
    • Q3 缩小锁粒度

  • kernel bypass
  • 非阻塞编程
  • 编程技巧
  • 编译优化
  • Q4 分析性能
  • 漫谈


概要

关于低延迟相关技术,需要我们掌握cpu cache工作原理、kernel bypass、非阻塞编程、编译优化、硬件优化( CPU pipelines,FPGA)、软件优化(高效算法和数据结构)


CPU Caches

Cacheline(缓存行):内存与缓存的交互总是以cachelline大小进行拷贝。比如:cpu传输一个整型变量时(4或8字节),采用的是一个cacheline大小(主流cpu是64字节)进行传输,可以理解cacheline为最小缓存单位。

缓存的类型:指令缓存(程序指令),数据缓存(程序数据),虚拟内存与物理内存的映射缓存。

缓存等级:cpu寄存器 -> L1缓存 -> L2 缓存 -> L3缓存 -> 主存, 越靠近cpu寄存器的位置速度越快。

缓存一致性:对在不同的缓存和主存之间的数据一致性处理。

时间局部性 Temporal Locality:指最近使用的数据更有可能再被使用。

空间局部性 Spatial Locality:如果内存中某个位置的数据正在被cpu所使用,那么靠近这个内存位置的内存数据很可能在不久的将来被引用。

缓存预热 :当数据第一次被cpu引用时,数据被从主存放置到缓存中。缓存预热是为了增加缓存命中,而预测即将使用的数据并把其加载到缓存中。

**CPU 绑定,pinning **: 指将进程和某个或者某几个 CPU 关联绑定,绑定后的进程只能在所关联的 CPU 上运行,可提高缓存命中率。


Q1 分析代码段性能

分别采用行、列访问数据的方式:

// row major order
std::vector<double> v(n * n);
double sum0.0;
for (std::size_t i &#61; 0; i < n; i&#43;&#43;)
for (std::size_t j &#61; 0; j < n ; j&#43;&#43;)
sum &#43;&#61; v[i * n &#43; j];


// column major order
std::vector<double> v(n * n);
double sum0.0;
for (std::size_t j &#61; 0; j < n; j&#43;&#43;)
for (std::size_t i &#61; 0; i < n ; i&#43;&#43;)
sum &#43;&#61; v[i * n &#43; j];


根据空间局部性和Cacheline&#xff0c;采用行式访问数据性能更好&#xff0c;因为cpu读取数据时采用cacheline大小将内存空间上线性数据加载到缓存中。即采用行方式遍历数据时&#xff0c;下一个数据已在Cache中&#xff0c;提高了Cache的命中率。


Q2 采用alignas进行性能优化

观察以下代码&#xff1a;

struct Data
char c;
char d;
;
Data data;
void thread1_func()
int sum &#61; 0;
for (int i &#61; 0; i < 100000000; &#43;&#43;i)
sum &#43;&#61; data.c;


void thread2_func()
int sum &#61; 0;
for (int i &#61; 0; i < 100000000; &#43;&#43;i)
data.d &#61; i % 256;


采用两个线程并发执行以上两个函数&#xff0c;因为Data结构的c\\d都是1个字节&#xff0c;而cacheline普遍为64个字节&#xff0c;所以当执行data.d的更新时&#xff0c;cpu会采用cacheline大小更新缓存到主存的数据&#xff0c;导致data.c无法命中缓存。因为data.d需要更新&#xff0c;那么我们可以设置缓存只更新data.d大小&#xff08;1个字节&#xff09;&#xff0c;所以可以使用alignas制定内存对齐大小。

struct alignas(1) Data
char c;
char d;
;

Q3 缩小锁粒度

long long sum &#61; 0;
void sumUp()
long long tmp &#61; 0;
for (int i &#61; 0; i < 10000; &#43;&#43;i)
tmp &#43;&#61; 1;

std::lock_guard<std::mutex> lockGuard(myMutex);
sum &#43;&#61; tmp;

当有多个线程对全局变量sum进行操作时&#xff0c;可以在对sum进行操作时才进行锁&#xff0c;而sum的累计先采用本地变量tmp进行计算后才更新sum变量。


kernel bypass

Kernel bypass是一种绕过OS与网络栈或者其他硬件进行数据交互的技术&#xff0c;通过减少用户态与内核态的数据拷贝&#xff08;也叫零拷贝zero-copy&#xff09;来提升性能。


非阻塞编程

通常将的NIO方式&#xff0c;比如sockets 或者 设计一套异步事件响应系统&#xff0c;在生产中常用Actor模型。
这一块的知识在前篇系列文章都有讲到过。


编程技巧

1、 避免动态内存分配
采用内存池&#xff08;对象池&#xff09;&#xff0c;减少内存的分配释放以及内存碎片的产生。

2、 采用位运算替换数值运算
比如 n/2 可以采用 n >> 1的方式性能更高。

3、利用cacheline&#xff0c;让即将使用到的数据更紧凑的在同一个cacheline大小范围内&#xff0c;提高cache命中率。

4、交易系统场景里需要使用到浮点数值来表示数据&#xff0c;比如股票价格10.01&#xff0c;而优化的方式是使用整型&#xff1a;1010&#xff08;price&#xff09;&#xff0c;小数点位为 2&#xff08;factor&#xff09;这种形式。

5、因为cacheline预加载cache&#xff0c;当数据量少时&#xff0c;采用线性查找比二分查找性能更好

6、


编译优化

采用__builtin_expect做分支预测。它是gcc编译器引入的一个指令&#xff0c;允许程序员将代码中最有可能执行的分支告诉编译器。具体的写法

__builtin_expect(EXP,N) // 其中EXP可以为变量&#xff0c;也可以为表达式

意思是&#xff0c;EXP&#61;&#61;n的概率很大。

//预测x更大几率为false
if (__builtin_expect(x, 0))
foo();
...

else
bar();
...

以上代码生成汇编&#xff1a;

cmp $x,0
jne _foo
_bar:
call bar
...
jmp after_if
_foo:
call foo
...
after_if:

简化版的cpu流水线&#xff08;cpu pipeline&#xff09;

分支预测器位于整个CPU核心流水线的差不多最前端部分&#xff0c;靠近IF的级。从指令缓存里面读取指令时&#xff0c;需要由分支预测器来判断从哪里读取。
所以采用分支预测实际上是优化了分支预测期的缓存。


Q4 分析性能

随机初始化数组值&#xff0c;然后统计随机数中大于等于128的值

const unsigned arraySize &#61; 32768;
int data[arraySize];
for (unsigned c &#61; 0; c < arraySize; &#43;&#43;c)
data[c] &#61; std::rand() % 256;


clock_t start &#61; clock();
long long sum &#61; 0;
for (unsigned i &#61; 0; i < 100000; &#43;&#43;i)
//primary loop
for (unsigned c &#61; 0; c < arraySize; &#43;&#43;c)
if (data[c] >&#61; 128)
sum &#43;&#61; data[c];



double elapsedTime &#61; static_cast<double>(clock() - start) / CLOCKS_PER_SEC;
std::cout << elapsedTime << endl;//17.979

初始化完数值后&#xff0c;先进行排序&#xff0c;再统计大于等于128的值

const unsigned arraySize &#61; 32768;
int data[arraySize];
for (unsigned c &#61; 0; c < arraySize; &#43;&#43;c)
data[c] &#61; std::rand() % 256;

//先进行排序
std::sort(data, data &#43; arraySize);

clock_t start &#61; clock();
long long sum &#61; 0;
for (unsigned i &#61; 0; i < 100000; &#43;&#43;i)
//primary loop
for (unsigned c &#61; 0; c < arraySize; &#43;&#43;c)
if (data[c] >&#61; 128)
sum &#43;&#61; data[c];



double elapsedTime &#61; static_cast<double>(clock() - start) / CLOCKS_PER_SEC;
std::cout << elapsedTime << endl; //5.909

排序后的执行性能提升了至少3倍&#xff0c;而且你使用vscode2019的诊断工具时&#xff0c;可以看到cpu有个跃式的进度跳动。
排序后的性能高是因为现代cpu都采用了长流水线工作方式&#xff0c;也即是前文讲到的cpu pipeline&#xff0c;在执行指令时会采用分支预测方式&#xff0c;在统计数值时:

if (data[c] >&#61; 128)
sum &#43;&#61; data[c];

而如果没有排序的话&#xff0c;分支预测的错误性更高且随机&#xff0c;那么指令缓存毫无意义&#xff0c;排序后使分支预测为常态。


漫谈

如果是高频交易系统的话&#xff0c;最好采用集中式服务&#xff0c;比如让数据、计算都在一个进程&#xff0c;让数据跟计算更紧凑&#xff0c;而不是采用当前的互联网微服务开发思维。

还有就是当前越来越重要的FPGA矩阵编程&#xff0c;可以饶过CPU在FPGA上进行市场数据分析


推荐阅读
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 深入解析:Synchronized 关键字在 Java 中对 int 和 Integer 对象的作用与影响
    深入探讨了 `Synchronized` 关键字在 Java 中对 `int` 和 `Integer` 对象的影响。尽管初看此题似乎简单,但其实质在于理解对象的概念。根据《Java编程思想》第二章的观点,一切皆为对象。本文详细分析了 `Synchronized` 关键字在不同数据类型上的作用机制,特别是对基本数据类型 `int` 和包装类 `Integer` 的区别处理,帮助读者深入理解 Java 中的同步机制及其在多线程环境中的应用。 ... [详细]
  • Android 构建基础流程详解
    Android 构建基础流程详解 ... [详细]
  • 本文深入解析了JDK 8中HashMap的源代码,重点探讨了put方法的工作机制及其内部参数的设定原理。HashMap允许键和值为null,但键为null的情况只能出现一次,因为null键在内部通过索引0进行存储。文章详细分析了capacity(容量)、size(大小)、loadFactor(加载因子)以及红黑树转换阈值的设定原则,帮助读者更好地理解HashMap的高效实现和性能优化策略。 ... [详细]
  • 本文回顾了作者初次接触Unicode编码时的经历,并详细探讨了ASCII、ANSI、GB2312、UNICODE以及UTF-8和UTF-16编码的区别和应用场景。通过实例分析,帮助读者更好地理解和使用这些编码。 ... [详细]
  • 字符串学习时间:1.5W(“W”周,下同)知识点checkliststrlen()函数的返回值是什么类型的?字 ... [详细]
  • 在分析Android的Audio系统时,我们对mpAudioPolicy->get_input进行了详细探讨,发现其背后涉及的机制相当复杂。本文将详细介绍这一过程及其背后的实现细节。 ... [详细]
  • 在工业过程控制系统中,由于被控对象的环境比较恶劣,干扰源比较多,仪器、仪表采集的信息常会受到干扰,所以在模拟系统中,为了消除干扰,常采用RC滤波电路,而在由工业控制计算机组成的自动 ... [详细]
  • 本文总结了在SQL Server数据库中编写和优化存储过程的经验和技巧,旨在帮助数据库开发人员提升存储过程的性能和可维护性。 ... [详细]
  • 本文总结了一些开发中常见的问题及其解决方案,包括特性过滤器的使用、NuGet程序集版本冲突、线程存储、溢出检查、ThreadPool的最大线程数设置、Redis使用中的问题以及Task.Result和Task.GetAwaiter().GetResult()的区别。 ... [详细]
  • 本文详细介绍了MySQL数据库的基础语法与核心操作,涵盖从基础概念到具体应用的多个方面。首先,文章从基础知识入手,逐步深入到创建和修改数据表的操作。接着,详细讲解了如何进行数据的插入、更新与删除。在查询部分,不仅介绍了DISTINCT和LIMIT的使用方法,还探讨了排序、过滤和通配符的应用。此外,文章还涵盖了计算字段以及多种函数的使用,包括文本处理、日期和时间处理及数值处理等。通过这些内容,读者可以全面掌握MySQL数据库的核心操作技巧。 ... [详细]
  • MySQL的查询执行流程涉及多个关键组件,包括连接器、查询缓存、分析器和优化器。在服务层,连接器负责建立与客户端的连接,查询缓存用于存储和检索常用查询结果,以提高性能。分析器则解析SQL语句,生成语法树,而优化器负责选择最优的查询执行计划。这一流程确保了MySQL能够高效地处理各种复杂的查询请求。 ... [详细]
  • 在《Cocos2d-x学习笔记:基础概念解析与内存管理机制深入探讨》中,详细介绍了Cocos2d-x的基础概念,并深入分析了其内存管理机制。特别是针对Boost库引入的智能指针管理方法进行了详细的讲解,例如在处理鱼的运动过程中,可以通过编写自定义函数来动态计算角度变化,利用CallFunc回调机制实现高效的游戏逻辑控制。此外,文章还探讨了如何通过智能指针优化资源管理和避免内存泄漏,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • 深入解析 Synchronized 锁的升级机制及其在并发编程中的应用
    深入解析 Synchronized 锁的升级机制及其在并发编程中的应用 ... [详细]
  • 深入解析C语言中结构体的内存对齐机制及其优化方法
    为了提高CPU访问效率,C语言中的结构体成员在内存中遵循特定的对齐规则。本文详细解析了这些对齐机制,并探讨了如何通过合理的布局和编译器选项来优化结构体的内存使用,从而提升程序性能。 ... [详细]
author-avatar
lovely月夜静悄悄知_302
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有